package com.corticon.dialogs.web.designer;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.net.URLDecoder;
import java.io.UnsupportedEncodingException;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.ModelMap;
import org.springframework.util.ClassUtils;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

import com.corticon.dialogs.dao.DataValidationTypeDAO;
import com.corticon.dialogs.dao.GenericDAO;
import com.corticon.dialogs.dao.LocaleDAO;
import com.corticon.dialogs.dao.LookupDAO;
import com.corticon.dialogs.dao.UserGroupDAO;
import com.corticon.dialogs.model.*;
import com.corticon.dialogs.util.BeanSupport;
import com.corticon.dialogs.util.Pager;
import com.corticon.dialogs.util.ValidationUtils;
import com.corticon.dialogs.util.security.CustomUserDetails;
import com.corticon.dialogs.util.security.SecurityUtils;
import com.corticon.dialogs.validation.GenericHibernateValidator;
import com.corticon.dialogs.validation.ValidationMessageFormatter;
import com.corticon.dialogs.web.BaseController;

/**
 * @author Lifan Yang
 * @since 2008-09-27
 */
@Controller
@SessionAttributes({"lookup", "groups", "locale", "countries", "languages", "dvts"})
public class LookupController extends BaseController {

    private static Log logger = LogFactory.getLog(LookupController.class);
    private final String MODEL_BASE_PACKAGE = "com.corticon.dialogs.model.";
    private final String DAO_SUFFIX = "DAO";
    private String dataValidationClassName = "dataValidation";
    private final String LIST_VIEW_NAME = "designer.lookup.list";

    // DataValidation View Names.
    private String dataValidationViewNamePrefix = "designer.datavalidation.";
    private String createDataValidationViewName = dataValidationViewNamePrefix + CREATE_REQUEST;
    private String editDataValidationViewName = dataValidationViewNamePrefix + EDIT_REQUEST;
    private String listDataValidationViewName = dataValidationViewNamePrefix + LIST_REQUEST;
    private String viewDataValidationViewName = dataValidationViewNamePrefix + VIEW_REQUEST;

    //
    @Resource
    private BeanSupport beanSupport;
    private GenericDAO dao;

    // DAOs
    @Resource
    private UserGroupDAO userGroupDAO;
    @Resource
    private LookupDAO lookupDAO;
    @Resource
    private LocaleDAO localeDAO;
    @Resource
    private DataValidationTypeDAO dataValidationTypeDAO;

    // Validators
    private CreateValidator createValidator;
    private EditValidator editValidator;

    /**
     *
     */
    private class CreateValidator extends GenericHibernateValidator<LookupBase> {

        private GenericDAO lookupDAO;

        public CreateValidator(ValidationMessageFormatter validationMessageFormatter) {
            super.validationMessageFormatter = validationMessageFormatter;
        }

        @Override
        public void complexValidate(Object object, Errors errors) {
            LookupBase lookup = (LookupBase) object;
            if (lookupDAO.findById(lookup.getCode()) != null) {
                errors.rejectValue("code", "lookup.code.isExisted", "This code is already existed.");
            }

            ValidationUtils.simpleStringValidate(errors, lookup.getCode(), "code",
                    "lookup.code.invalid");
        }

        public void setLookupDAO(GenericDAO lookupDAO) {
            this.lookupDAO = lookupDAO;
        }
    }

    private class EditValidator extends GenericHibernateValidator<LookupBase> {

        public EditValidator(ValidationMessageFormatter validationMessageFormatter) {
            super.validationMessageFormatter = validationMessageFormatter;
        }
    }

    @PostConstruct
    public void buildValidators() {
        createValidator = new CreateValidator(validationMessageFormatter);
        editValidator = new EditValidator(validationMessageFormatter);
    }

    /**
     * List themes according to the pagination infos.
     *
     * @param page The current page number.
     * @return
     */
    @RequestMapping(method = RequestMethod.GET)
    public String index(@RequestParam(required = false) Integer page,
                        HttpServletRequest request) {
        logger.info("list lookup");
        return getViewNameByRequestURI(request);
    }

    @RequestMapping(value = CREATE_REQUEST, method = RequestMethod.GET)
    public String getCreate(@RequestParam String lookupType, ModelMap model,
                            HttpServletRequest request) {

        setCreate(request);

        lookupType = getUniqueParameter(lookupType);

        loadEntityList(model, lookupType);

        // Init the lookup object.
        LookupBase lookup = getLookupEntity(lookupType);
        lookup.init();

        model.addAttribute("lookup", lookup);
        request.setAttribute("lookupType", lookupType);

        request.setAttribute(DEFAULT_MESSAGE_KEY, getMessage("lookup.create.message"));

        readMessages(defaultMessageKeys, request);

        if (!lookupType.equals(dataValidationClassName)) {
            return getViewNameByRequestURI(request);
        } else {
            return createDataValidationViewName;
        }
    }

    @Transactional
    @RequestMapping(value = CREATE_REQUEST, method = RequestMethod.POST)
    public String postCreate(@RequestParam String submitAction, @RequestParam String lookupType,
                             @ModelAttribute("lookup") LookupBase lookup, Errors errors, HttpServletRequest request,
                             SessionStatus sessionStatus) {

        setCreate(request);

        lookupType = getUniqueParameter(lookupType);

        dao = loadDAO(lookupType);

        createValidator.setLookupDAO(dao);

        // Validation
        Map<String, List<String>> errorMessages = createValidator.invokeValidate(lookup,
                errors, request.getLocale());

        if (errors.hasErrors()) {
            request.setAttribute("errors", errorMessages);
            request.setAttribute("lookupType", lookupType);
            return getViewNameByRequestURI(request);
        }

        setModifiedByInfo(lookup);

        dao.save(lookup);

        sessionStatus.setComplete();

        addMessages("message=lookup.create.success", request, messageSource, null);

        return redirectBySubmitAction(submitAction, request, "code=" + lookup.getCode(), "lookupType=" + lookupType);
    }

    @Transactional(readOnly = true)
    @RequestMapping(value = EDIT_REQUEST, method = RequestMethod.GET)
    public String getEdit(@RequestParam String lookupType, @RequestParam String code, ModelMap model,
                          HttpServletRequest request) {

        setEdit(request);

        lookupType = getUniqueParameter(lookupType);

        dao = loadDAO(lookupType);

        loadEntityList(model, lookupType);

        LookupBase lookup = (LookupBase) dao.findById(code);


        if (lookup == null) {
            // The lookup is not existed.
        }

        if (lookup.getUserGroup() != null && BasicGroup.ALL.toString().equals(lookup.getUserGroup().
                getName()) && SecurityUtils.getCurrentUserPermissions().contains(
                PermissionType.GROUP_ADMIN.value())) {
            return redirectTo403();
        }else{
            CustomUserDetails currentUser = SecurityUtils.getCurrentUser();
		    User user = userDAO.findById(currentUser.getUsername());
            if (user.getUserGroup() != lookup.getModifiedBy().getUserGroup()
                    && SecurityUtils.getCurrentUserPermissions().contains(
                    PermissionType.GROUP_ADMIN.value())){
                return redirectTo403();
            }

        }

        lookup.init();
        model.addAttribute("lookup", lookup);
        request.setAttribute("lookupType", lookupType);

        request.setAttribute(DEFAULT_MESSAGE_KEY, getMessage("lookup.edit.message"));

        readMessages(defaultMessageKeys, request);

        if (lookupType.equals(dataValidationClassName)) {
            return editDataValidationViewName;
        } else {
            return getViewNameByRequestURI(request);
        }
    }

    @Transactional
    @RequestMapping(value = EDIT_REQUEST, method = RequestMethod.POST)
    public String postEdit(@RequestParam String submitAction, @RequestParam String lookupType,
                           @ModelAttribute("lookup") LookupBase lookup, Errors errors, HttpServletRequest request,
                           SessionStatus sessionStatus) {

        setEdit(request);

        lookupType = getUniqueParameter(lookupType);

        // Validation
        Map<String, List<String>> errorMessages = editValidator.invokeValidate(lookup,
                errors, request.getLocale());

        if (errors.hasErrors()) {
            request.setAttribute("errors", errorMessages);
            request.setAttribute("lookupType", lookupType);
            return getViewNameByRequestURI(request);
        }

        dao = loadDAO(lookupType);

        setModifiedByInfo(lookup);

        dao.update(lookup);

        sessionStatus.setComplete();

        readMessages(defaultMessageKeys, request);

        addMessages("message=lookup.edit.success", request, messageSource, null);

        return redirectBySubmitAction(submitAction, request, "code=" + lookup.getCode(), "lookupType=" + lookupType);
    }

    @Transactional(readOnly = true)
    @RequestMapping(value = LIST_REQUEST, method = RequestMethod.GET)
    public String list(@RequestParam String lookupType, @RequestParam(required = false) Integer page,
                       HttpServletRequest request) {

        lookupType = getUniqueParameter(lookupType);

        dao = loadDAO(lookupType);

        Pager pager = constructPager(page, request);

        // Add sort infos
        pager = setPagerSort(pager, "code", request);

        // Add request params.
        pager.setRequestParameters("&lookupType=" + lookupType);

        List<LookupBase> lookups = new ArrayList<LookupBase>();
        List<String> groupNames = new ArrayList<String>();

        List<String> permissions = SecurityUtils.getCurrentUserPermissions();

        if (permissions.contains(PermissionType.SYS_ADMIN.value())) {
            pager.setNumOfRecords(dao.getCountAll());
            lookups = dao.getPageOfDataAll(pager);

            // Find all of groups in the system.
            List<UserGroup> groups = userGroupDAO.findAll();
            // Add the group name into the groupNames list.
            for (UserGroup group : groups) {
                groupNames.add(group.getName());
            }
        } else if (permissions.contains(PermissionType.PORTAL_USER.value())) {
            LookupBase lookupExample = getLookupEntity(lookupType);
            lookupExample.setUserGroup(new UserGroup(BasicGroup.ALL.toString()));

            pager.setNumOfRecords(dao.getCountByExample(lookupExample));
            lookups = dao.getPageOfDataByExample(lookupExample, pager);

            // Add the group 'ALL' into the groupNames list.
            groupNames.add(BasicGroup.ALL.toString());
        } else {
            // This lookup example is used to load the lookups with the group 'ALL'.
            LookupBase lookupGroupAllExample = getLookupEntity(lookupType);
            lookupGroupAllExample.setUserGroup(new UserGroup(BasicGroup.ALL.toString()));

            // Get the current user.
            CustomUserDetails currentUser = SecurityUtils.getCurrentUser();

            // This lookup example is used to load the lookups with the current user group.
            LookupBase lookupCurrentGroupExample = getLookupEntity(lookupType);
            lookupCurrentGroupExample.setUserGroup(new UserGroup(currentUser.getGroupName()));

            Object[] unionExamples = {lookupGroupAllExample, lookupCurrentGroupExample};

            pager.setNumOfRecords(dao.getCountByUnionExamples(unionExamples));
            lookups = dao.getPageOfDataByUnionExamples(unionExamples, pager);

            // Add the group 'ALL' and the group of the current user into the groupNames list.
            groupNames.add(BasicGroup.ALL.toString());
            groupNames.add(currentUser.getGroupName());
        }


        request.setAttribute("pager", pager);
        request.setAttribute("order", request.getParameter("sortBy"));
        request.setAttribute("column", request.getParameter("sortColumn"));

        request.setAttribute("lookups", lookups);
        request.setAttribute("pager", pager);
        request.setAttribute("groups", groupNames);


        if (lookups != null && lookups.size() > 0) {
            request.setAttribute("foundMessage", getMessage(String.valueOf(pager.getNumOfRecords()),
                    publicFoundMessage));
        } else {
            request.setAttribute("foundMessage", getMessage("", publicNotFoundMessage));
        }
        readMessages(defaultMessageKeys, request);
        if (lookupType.equals(dataValidationClassName)) {
            return listDataValidationViewName;
        } else {
            return getViewNameByRequestURI(request);
        }
    }

    @Transactional(readOnly = true)
    @RequestMapping(value = VIEW_REQUEST, method = RequestMethod.GET)
    public String view(@RequestParam String code, @RequestParam String lookupType,
                       HttpServletRequest request) {

        lookupType = getUniqueParameter(lookupType);

        dao = loadDAO(lookupType);

        LookupBase lookup = (LookupBase) dao.findById(code);

        if (!checkGroupPermission(lookup)) {
            return redirectTo403();
        }

        request.setAttribute("lookup", lookup);
        request.setAttribute("lookupType", lookupType);

        readMessages(defaultMessageKeys, request);

        if (lookupType.equals(dataValidationClassName)) {
            return viewDataValidationViewName;
        } else {
            return getViewNameByRequestURI(request);
        }
    }

    @Transactional(readOnly = true)
    @RequestMapping(method = RequestMethod.GET)
    public String search(@RequestParam String lookupType,
                         @RequestParam(required = false) String keywords,
                         @RequestParam(required = false) String groupName,
                         @RequestParam(required = false) Integer page, HttpServletRequest request) {

        // Get the lookup Class according to the type.
        lookupType = StringUtils.capitalize(lookupType);

        lookupType = getUniqueParameter(lookupType);

        Class lookupClass = ClassUtils.resolveClassName(MODEL_BASE_PACKAGE + lookupType, null);

        // Get the current user.
        CustomUserDetails currentUser = SecurityUtils.getCurrentUser();
        List<String> permissions = currentUser.getPermissions();

        // Get the groups name should be searched according to the permission of the current user.
        List<String> groupNames = new ArrayList<String>();
        if (!permissions.contains(PermissionType.SYS_ADMIN.value()) && !permissions.contains(
                PermissionType.PORTAL_USER.value())) {
            // If the current user is a group admin, reviewer or author.
            if (StringUtils.isEmpty(groupName)) {
                groupNames.add(BasicGroup.ALL.toString());
                groupNames.add(currentUser.getGroupName());
            } else {
                groupNames.add(groupName);
            }
        } else if (permissions.contains(PermissionType.PORTAL_USER.value())) {
            // If the current user is a portal user.
            groupNames.add(BasicGroup.ALL.toString());
        } else {
            // If the current user is a sys admin.
            if (StringUtils.isNotEmpty(groupName)) {
                groupNames.add(groupName);
            }
        }

        // Construct the pager object and set the pager into the request scope.
        Pager pager = constructPager(page, request);
        // Add sort infos
        pager = setPagerSort(pager, "code", request);
        
        pager.setNumOfRecords(lookupDAO.getCountForSearch(lookupClass, keywords, groupNames));
        pager.setRequestParameters("&lookupType=" + lookupType + "&keywords=" + keywords +
                "&groupName=" + groupName);
        request.setAttribute("pager", pager);
        request.setAttribute("order", request.getParameter("sortBy"));
		request.setAttribute("column", request.getParameter("sortColumn"));

        // To search, then set the results into the request scope.
        List<LookupBase> results = new ArrayList<LookupBase>();
        results = lookupDAO.search(lookupClass, keywords, groupNames, pager);
        request.setAttribute("lookups", results);

        /*
        Reset the groupNames list for the dropdown on the page.
         */
        if (permissions.contains(PermissionType.SYS_ADMIN.value())) {
            // Find all of groups in the system.
            List<UserGroup> groups = userGroupDAO.findAll();

            // Add the group name into the groupNames list.
            for (UserGroup group : groups) {
                groupNames.add(group.getName());
            }
        } else if (permissions.contains(PermissionType.PORTAL_USER.value())) {
            // Portal User can only search the lookups in the group 'ALL'.
            if (!BasicGroup.ALL.toString().equals(groupName)) {
                return redirectTo403();
            }
        } else {
            groupNames.clear();
            groupNames.add(BasicGroup.ALL.toString());
            groupNames.add(currentUser.getGroupName());

            // Other users can only search the lookups in the group 'ALL' or the same group with him.
            if (StringUtils.isNotBlank(groupName) && !groupNames.contains(groupName)) {
                return redirectTo403();
            }
        }

        if (results != null && results.size() > 0) {
            request.setAttribute("foundMessage", getMessage(String.valueOf(pager.getNumOfRecords()),
                    publicFoundMessage));
        } else {
            request.setAttribute("foundMessage", getMessage("", publicNotFoundMessage));
        }
        request.setAttribute("groups", groupNames);

        return LIST_VIEW_NAME;
    }

    /**
     * Default method used to cancel an action. Cancel operation can return to the target you want
     * to by the "from" param.
     *
     * @param request
     * @return
     */
    @RequestMapping(value = CANCEL_REQUEST, method = RequestMethod.GET)
    @Override
    public String cancel(HttpServletRequest request) {
        String fromUrl = request.getParameter("from");

        boolean hasMsg = true;
        if (StringUtils.isNotEmpty(request.getParameter("hasMsg")))
            hasMsg = Boolean.parseBoolean(request.getParameter("hasMsg"));

        if (hasMsg)
            request.getSession().setAttribute(DEFAULT_MESSAGE_KEY, getMessage("general.action.cancel"));

        if (StringUtils.isNotBlank(fromUrl)) {
            return redirectPrefix + fromUrl + "&lookupType=" + request.getParameter("lookupType");
        }

        return redirectPrefix + LIST_REQUEST + requestSuffix + "?lookupType=" + request.getParameter("lookupType");
    }

    // ********** Helper Methods ********** //
    /**
     * Get the lookupDAO by name.
     *
     * @return
     */
    private GenericDAO loadDAO(String entityName) {
        entityName = StringUtils.uncapitalize(entityName);
        String daoName = entityName + DAO_SUFFIX;
        return (GenericDAO) beanSupport.findBeanByName(daoName);
    }

    /**
     * Get the user's usergroup according to user permission
     *
     * @param currentAdmin
     * @return
     */
    private List<UserGroup> getUserGroupList(CustomUserDetails currentAdmin) {

        List<UserGroup> groups = new ArrayList<UserGroup>();

        List<String> permissions = SecurityUtils.getCurrentUserPermissions();

        if (permissions.contains(PermissionType.SYS_ADMIN.value())) {
            groups = userGroupDAO.findAll();
        } else if (permissions.contains(PermissionType.GROUP_ADMIN.value())) {
            // TODO: no need dropdown.
            UserGroup userGroup = userGroupDAO.load(currentAdmin.getGroupName());
            groups.add(userGroup);
        }
        return groups;
    }

    protected String redirectBySubmitAction(String submitAction, HttpServletRequest request, String... params) {
        String startingPoint = "";
        if (request != null && StringUtils.isNotEmpty(request.getParameter(FROM_PARAM))) {
            startingPoint = request.getParameter(FROM_PARAM);
        }

        if (SUBMIT.equalsIgnoreCase(submitAction)) {
            return redirectPrefix + VIEW_REQUEST + requestSuffix + "?" + params[0] + "&" + params[1] + "&from=" + startingPoint;
        } else if (SUBMIT_AND_NEW.equalsIgnoreCase(submitAction)) {
            return redirectPrefix + CREATE_REQUEST + requestSuffix + "?" + params[1] + "&from=" + startingPoint;
        } else if (SUBMIT_AND_EXIT.equalsIgnoreCase(submitAction)) {
            if (StringUtils.isNotEmpty(startingPoint)) {
                try {
                    startingPoint = URLDecoder.decode(startingPoint, "utf-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    return redirectPrefix + LIST_REQUEST + requestSuffix + "?" + params[1];
                }
                return redirectPrefix + startingPoint;
            }
        }
        return redirectPrefix + LIST_REQUEST + requestSuffix + "?" + params[1];
    }

    private LookupBase getLookupEntity(String lookupType) {
        lookupType = StringUtils.capitalize(lookupType);
        Class lookupClass = ClassUtils.resolveClassName(MODEL_BASE_PACKAGE + lookupType, null);
        return (LookupBase) BeanUtils.instantiateClass(lookupClass);
    }

    /**
     * Load the user group and the data validation type list into {@link ModelMap}.
     */
    private void loadEntityList(ModelMap model, String lookupType) {
        CustomUserDetails currentAdmin = SecurityUtils.getCurrentUser();
        List<UserGroup> groups = this.getUserGroupList(currentAdmin);
        model.addAttribute("groups", groups);

        // If the lookup is a DataValidationType, then load the DataValidationType list.
        if (lookupType.equals(dataValidationClassName)) {
            List<DataValidationType> dvts = dataValidationTypeDAO.findAll();
            model.addAttribute("dvts", dvts);
        }
    }

    /**
     * Check whether the current user has the permission to access the lookup.
     *
     * @param lookup
     * @return
     */
    private boolean checkGroupPermission(LookupBase lookup) {
        List<String> permissions = SecurityUtils.getCurrentUserPermissions();
        if (permissions.contains(PermissionType.SYS_ADMIN.value())) {
            return true;
        } else {
            CustomUserDetails currentUser = SecurityUtils.getCurrentUser();
            if (lookup.getUserGroup().getName().equals(BasicGroup.ALL.toString()) || currentUser.
                    getGroupName().equals(lookup.getUserGroup().getName())) {
                return true;
            } else {
                return false;
            }
        }
    }

    private String getUniqueParameter(String parameter) {
        if (parameter.indexOf(",") > -1) {
            return parameter.split(",")[0];
        } else {
            return parameter;
        }
    }
}
